﻿using System;
using System.Collections.Generic;
using System.Linq;
using Ciloci.Flee;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.SharePoint;
using System.Text;
using System.Collections;
using Evolution.Utilities;
using System.Web.UI;

namespace Evolution.Expression
{
    public class ExpressionInterpreter
    {
        #region Constructors

        public ExpressionInterpreter(SPWeb web, Page page)
        {
            Global global = new Global();

            this.Context = new ExpressionContext(global);

            global.ExpressionContext = this.Context;

            this.Context.Options.OwnerMemberAccess = BindingFlags.Public;

            this.Context.ParserOptions.DecimalSeparator = '.';
            this.Context.ParserOptions.FunctionArgumentSeparator = ',';
            this.Context.ParserOptions.RecreateParser();

            this.AddType(typeof(DateTime), "DateTime");
            this.AddType(typeof(String), "String");
            this.AddType(typeof(Math), "Math");
            this.AddType(typeof(Regex), "Regex");
            this.AddType(typeof(Expression), "Expression");
            this.AddType(typeof(DateTimePart), "DateTimePart");

            if (web != null)
            {   
                this.AddObject("CurrentWeb", web);
                this.AddObject("CurrentSite", web.Site);
                this.AddObject("CurrentWebApplication", web.Site.WebApplication);
                this.AddObject("CurrentUser", web.CurrentUser);

                if (page != null)
                    this.AddObject("CurrentFile", web.GetFile(page.Request.Path));
            }

            if (page != null)
            {
                this.AddObject("Page", page);
                this.AddObject("Request", page.Request);
                this.AddObject("Session", page.Session);
                this.AddObject("Application", page.Application);
                this.AddObject("Cookies", page.Request.Cookies);
                this.AddObject("Cache", page.Cache);
            }
        }

        public ExpressionInterpreter(SPWeb web)
            : this(web, null)
        {
        }

        public ExpressionInterpreter(Page page)
            : this(null, page)
        {
        }

        public ExpressionInterpreter()
            : this(null, null)
        {
        }
        
        #endregion

        #region Properties
		
        private ExpressionContext Context { get; set; }
        
	    #endregion
        
        #region Methods

        public void AddType(Type t)
        {
            this.Context.Imports.AddType(t);
        }

        public void AddType(Type t, string ns)
        {
            this.Context.Imports.AddType(t, ns);
        }

        public void AddObject(string name, object value)
        {
            this.Context.Variables.Add(name, value);
        }

        public string Evaluate(string text)
        {
            IDynamicExpression e = this.Context.CompileDynamic(text);
            return e.Evaluate().ToString();
        }

        public bool EvaluateCondition(string text)
        {
            IGenericExpression<bool> e = this.Context.CompileGeneric<bool>(text);
            return e.Evaluate();
        }

        public string ParseExpression(string text)
        {
            if (StringUtility.IsNullOrEmpty(text))
                return String.Empty;

            StringBuilder result = new StringBuilder();
            int index = -1, index1 = -1;
            string source = text;
            string code;
            while ((index = source.IndexOf(Expression.OpeningSymbol)) != -1)
            {
                result.Append(source.Substring(0, index));
                index1 = source.IndexOf(Expression.ClosingSymbol, index + Expression.OpeningSymbol.Length);
                if (index1 == -1)
                    throw new InvalidOperationException(String.Format("Missing the closing symbol \"{0}\" of the expression.", Expression.ClosingSymbol));

                code = source.Substring(index + Expression.OpeningSymbol.Length, index1 - index - Expression.OpeningSymbol.Length).Trim();

                source = source.Substring(index1 + Expression.ClosingSymbol.Length);

                IDynamicExpression e = this.Context.CompileDynamic(code);
                result.Append(e.Evaluate());
            }
            result.Append(source);

            return result.ToString();
        }

        #endregion

        #region Inner Class "Expression"

        public static class Expression
        {
            public const string OpeningSymbol = "!$";
            public const string ClosingSymbol = "$";
        }

        #endregion

        #region Inner Class "Global"

        public class Global
        {
            #region Constructors
            
            public Global()
            {
                this.ExpressionContext = null;
            }

            #endregion
            
            #region Properties

            public ExpressionContext ExpressionContext { get; internal set; }

            #endregion

            #region Methods

            #region Inject -> T

            private T Inject<T>(IEnumerable<T> a, T initValue, string accumulator, string element, string expression)
            {
                T result = default(T);

                object oldValue1 = null;
                object oldValue2 = null;
                bool exists1 = false;
                bool exists2 = false;

                if ((exists1 = this.ExpressionContext.Variables.ContainsKey(accumulator)))
                    oldValue1 = this.ExpressionContext.Variables[accumulator];
                else
                    this.ExpressionContext.Variables.DefineVariable(accumulator, typeof(T));

                this.ExpressionContext.Variables[accumulator] = initValue;

                if ((exists2 = this.ExpressionContext.Variables.ContainsKey(element)))
                    oldValue2 = this.ExpressionContext.Variables[element];

                bool created1 = exists1;
                bool created2 = exists2;
                foreach (T value in a)
                {
                    if (!created2)
                    {
                        this.ExpressionContext.Variables.DefineVariable(element, typeof(T));
                        this.ExpressionContext.Variables[element] = value;
                        created2 = true;
                    }
                    else
                        this.ExpressionContext.Variables[element] = value;

                    IGenericExpression<T> ex = this.ExpressionContext.CompileGeneric<T>(expression);
                    this.ExpressionContext.Variables[accumulator] = ex.Evaluate();
                }

                result = this.ExpressionContext.Variables.GetVariableValueInternal<T>(accumulator);

                if (exists1)
                    this.ExpressionContext.Variables[accumulator] = oldValue1;
                else
                    this.ExpressionContext.Variables.Remove(accumulator);

                if (exists2)
                    this.ExpressionContext.Variables[element] = oldValue2;
                else
                    this.ExpressionContext.Variables.Remove(element);

                return result;
            }

            public DateTime Inject(IEnumerable<DateTime> a, DateTime initValue, string accumulator, string element, string expression)
            {
                return Inject<DateTime>(a, initValue, accumulator, element, expression);
            }

            public string Inject(IEnumerable<string> a, string initValue, string accumulator, string element, string expression)
            {
                return Inject<string>(a, initValue, accumulator, element, expression);
            }

            public SByte Inject(IEnumerable<SByte> a, SByte initValue, string accumulator, string element, string expression)
            {
                return Inject<SByte>(a, initValue, accumulator, element, expression);
            }

            public Single Inject(IEnumerable<Single> a, Single initValue, string accumulator, string element, string expression)
            {
                return Inject<Single>(a, initValue, accumulator, element, expression);
            }

            public Int64 Inject(IEnumerable<Int64> a, Int64 initValue, string accumulator, string element, string expression)
            {
                return Inject<Int64>(a, initValue, accumulator, element, expression);
            }

            public Int32 Inject(IEnumerable<Int32> a, Int32 initValue, string accumulator, string element, string expression)
            {
                return Inject<Int32>(a, initValue, accumulator, element, expression);
            }

            public Int16 Inject(IEnumerable<Int16> a, Int16 initValue, string accumulator, string element, string expression)
            {
                return Inject<Int16>(a, initValue, accumulator, element, expression);
            }

            public double Inject(IEnumerable<double> a, double initValue, string accumulator, string element, string expression)
            {
                return Inject<double>(a, initValue, accumulator, element, expression);
            }

            public decimal Inject(IEnumerable<decimal> a, decimal initValue, string accumulator, string element, string expression)
            {
                return Inject<decimal>(a, initValue, accumulator, element, expression);
            }

            public bool Inject(IEnumerable<bool> a, bool initValue, string accumulator, string element, string expression)
            {
                return Inject<bool>(a, initValue, accumulator, element, expression);
            }

            public byte Inject(IEnumerable<byte> a, byte initValue, string accumulator, string element, string expression)
            {
                return Inject<byte>(a, initValue, accumulator, element, expression);
            }

            public char Inject(IEnumerable<char> a, char initValue, string accumulator, string element, string expression)
            {
                return Inject<char>(a, initValue, accumulator, element, expression);
            }

            #endregion

            #region TypeOf -> Type

            public Type TypeOf(object value)
            {
                if (value == null)
                    return null;

                return value.GetType();
            }

            #endregion

            #region Seq -> object

            public object Seq(params object[] args)
            {
                return args.Length == 0 ? 0 : args[args.Length - 1];
            }

            #endregion

            #region Eval -> object

            public object Eval(string text)
            {
                IDynamicExpression ex = this.ExpressionContext.CompileDynamic(text);
                return ex.Evaluate();
            }

            #endregion

            #region Text functions

            public string Format(string source, bool esc)
            {
                Regex re = new Regex("\\{\\s*(\\w+)\\s*\\}");
                return re.Replace(esc ? source.Replace("'", "\"") : source,
                    delegate(Match m)
                    {
                        if (this.ExpressionContext.Variables.ContainsKey(m.Groups[1].Value))
                            return Convert.ToString(this.ExpressionContext.Variables[m.Groups[1].Value]);

                        return String.Empty;
                    });
            }

            public string Format(string source)
            {
                return this.Format(source, false);
            }

            // source = "The value of {x.Key} is {x.Value}" -> return = "\"The value of \" + x.Key + \" is \" + x.Value \"\""
            public string FormatExpression(string source)
            {
                Regex re = new Regex("\\{\\s*([^}]+)\\s*\\}");
                string result = "\"" + re.Replace(source,
                    delegate(Match m)
                    {
                        return "\" + " + m.Groups[1].Value + " + \"";
                    }) + "\"";

                if (result.StartsWith("\"\" + "))
                    result = result.Substring(5);

                if (result.EndsWith(" + \"\""))
                    result = result.Substring(0, result.Length - 5);

                return result;
            }

            public string ToString(IEnumerable e, string sep)
            {
                IDictionary d = e as IDictionary;
                string result = String.Empty;

                if (d == null)
                    foreach (object o in e)
                        result += o.ToString() + sep;
                else
                    foreach (object o in d.Values)
                        result += o.ToString() + sep;

                return String.IsNullOrEmpty(result) ? "" : result.Substring(0, result.Length - sep.Length);
            }

            public string ToString(IEnumerable e)
            {
                return this.ToString(e, ", ");
            }

            #endregion

            #region Iterate (Iterate, IterateDist, IterateDict) -> []

            public object[] Iterate(IEnumerable a, string variableName, string expression)
            {
                ArrayList list = new ArrayList();

                object oldValue = null;
                bool exists = false;

                if ((exists = this.ExpressionContext.Variables.ContainsKey(variableName)))
                    oldValue = this.ExpressionContext.Variables[variableName];

                bool created = exists;
                foreach (object s in a)
                {
                    if (!created)
                    {
                        this.ExpressionContext.Variables.Add(variableName, s);
                        created = true;
                    }
                    else
                        this.ExpressionContext.Variables[variableName] = s;

                    IDynamicExpression ex = this.ExpressionContext.CompileDynamic(expression);
                    list.Add(ex.Evaluate());
                }

                if (exists)
                    this.ExpressionContext.Variables[variableName] = oldValue;
                else
                    this.ExpressionContext.Variables.Remove(variableName);

                return list.ToArray();
            }

            public object[] IterateDist(IEnumerable a, string variableName, string expression)
            {
                ArrayList list = new ArrayList();

                object oldValue = null;
                bool exists = false;

                if ((exists = this.ExpressionContext.Variables.ContainsKey(variableName)))
                    oldValue = this.ExpressionContext.Variables[variableName];

                bool created = exists;
                foreach (object s in a)
                {
                    if (!created)
                    {
                        this.ExpressionContext.Variables.DefineVariable(variableName, typeof(object));
                        this.ExpressionContext.Variables[variableName] = s;
                        created = true;
                    }
                    else
                        this.ExpressionContext.Variables[variableName] = s;

                    IDynamicExpression ex = this.ExpressionContext.CompileDynamic(expression);
                    list.Add(ex.Evaluate());
                }

                if (exists)
                    this.ExpressionContext.Variables[variableName] = oldValue;
                else
                    this.ExpressionContext.Variables.Remove(variableName);

                return list.ToArray();
            }

            public object[] IterateDict(IDictionary a, string variableName, string expression)
            {
                ArrayList list = new ArrayList();

                object oldValue = null;
                bool exists = false;

                if ((exists = this.ExpressionContext.Variables.ContainsKey(variableName)))
                    oldValue = this.ExpressionContext.Variables[variableName];

                bool created = exists;
                foreach (DictionaryEntry s in a)
                {
                    if (!created)
                    {
                        this.ExpressionContext.Variables.DefineVariable(variableName, typeof(DictionaryEntry));
                        this.ExpressionContext.Variables[variableName] = s;
                        created = true;
                    }
                    else
                        this.ExpressionContext.Variables[variableName] = s;

                    IDynamicExpression ex = this.ExpressionContext.CompileDynamic(expression);
                    list.Add(ex.Evaluate());
                }

                if (exists)
                    this.ExpressionContext.Variables[variableName] = oldValue;
                else
                    this.ExpressionContext.Variables.Remove(variableName);

                return list.ToArray();
            }

            #endregion

            #region Select (Select, SelectDist, SelectDict) -> []
           
            public object[] Select(IEnumerable a, string variableName, string expression)
            {
                ArrayList list = new ArrayList();

                object oldValue = null;
                bool exists = false;

                if ((exists = this.ExpressionContext.Variables.ContainsKey(variableName)))
                    oldValue = this.ExpressionContext.Variables[variableName];

                bool created = exists;
                foreach (object s in a)
                {
                    if (!created)
                    {
                        this.ExpressionContext.Variables.Add(variableName, s);
                        created = true;
                    }
                    else
                        this.ExpressionContext.Variables[variableName] = s;

                    IGenericExpression<bool> ex = this.ExpressionContext.CompileGeneric<bool>(expression);
                    if (ex.Evaluate())
                        list.Add(s);
                }

                if (exists)
                    this.ExpressionContext.Variables[variableName] = oldValue;
                else
                    this.ExpressionContext.Variables.Remove(variableName);

                return list.ToArray();
            }
            
            public object[] SelectDist(IEnumerable a, string variableName, string expression)
            {
                ArrayList list = new ArrayList();

                object oldValue = null;
                bool exists = false;

                if ((exists = this.ExpressionContext.Variables.ContainsKey(variableName)))
                    oldValue = this.ExpressionContext.Variables[variableName];

                bool created = exists;
                foreach (object s in a)
                {
                    if (!created)
                    {
                        this.ExpressionContext.Variables.DefineVariable(variableName, typeof(object));
                        this.ExpressionContext.Variables[variableName] = s;
                        created = true;
                    }
                    else
                        this.ExpressionContext.Variables[variableName] = s;

                    IGenericExpression<bool> ex = this.ExpressionContext.CompileGeneric<bool>(expression);
                    if (ex.Evaluate())
                        list.Add(s);
                }

                if (exists)
                    this.ExpressionContext.Variables[variableName] = oldValue;
                else
                    this.ExpressionContext.Variables.Remove(variableName);

                return list.ToArray();
            }

            public DictionaryEntry[] SelectDict(IDictionary a, string variableName, string expression)
            {
                List<DictionaryEntry> list = new List<DictionaryEntry>();

                object oldValue = null;
                bool exists = false;

                if ((exists = this.ExpressionContext.Variables.ContainsKey(variableName)))
                    oldValue = this.ExpressionContext.Variables[variableName];

                bool created = exists;
                foreach (DictionaryEntry s in a)
                {
                    if (!created)
                    {
                        this.ExpressionContext.Variables.DefineVariable(variableName, typeof(DictionaryEntry));
                        this.ExpressionContext.Variables[variableName] = s;
                        created = true;
                    }
                    else
                        this.ExpressionContext.Variables[variableName] = s;

                    IGenericExpression<bool> ex = this.ExpressionContext.CompileGeneric<bool>(expression);
                    if (ex.Evaluate())
                        list.Add(s);
                }

                if (exists)
                    this.ExpressionContext.Variables[variableName] = oldValue;
                else
                    this.ExpressionContext.Variables.Remove(variableName);

                return list.ToArray();
            }

            #endregion

            #region Reject (Reject, RejectDist, RejectDict) -> []
            
            public object[] Reject(IEnumerable a, string variableName, string expression)
            {
                ArrayList list = new ArrayList();

                object oldValue = null;
                bool exists = false;

                if ((exists = this.ExpressionContext.Variables.ContainsKey(variableName)))
                    oldValue = this.ExpressionContext.Variables[variableName];

                bool created = exists;
                foreach (object s in a)
                {
                    if (!created)
                    {
                        this.ExpressionContext.Variables.Add(variableName, s);
                        created = true;
                    }
                    else
                        this.ExpressionContext.Variables[variableName] = s;

                    IGenericExpression<bool> ex = this.ExpressionContext.CompileGeneric<bool>(expression);
                    if (!ex.Evaluate())
                        list.Add(s);
                }

                if (exists)
                    this.ExpressionContext.Variables[variableName] = oldValue;
                else
                    this.ExpressionContext.Variables.Remove(variableName);

                return list.ToArray();
            }
            
            public object[] RejectDist(IEnumerable a, string variableName, string expression)
            {
                ArrayList list = new ArrayList();

                object oldValue = null;
                bool exists = false;

                if ((exists = this.ExpressionContext.Variables.ContainsKey(variableName)))
                    oldValue = this.ExpressionContext.Variables[variableName];

                bool created = exists;
                foreach (object s in a)
                {
                    if (!created)
                    {
                        this.ExpressionContext.Variables.DefineVariable(variableName, typeof(object));
                        this.ExpressionContext.Variables[variableName] = s;
                        created = true;
                    }
                    else
                        this.ExpressionContext.Variables[variableName] = s;

                    IGenericExpression<bool> ex = this.ExpressionContext.CompileGeneric<bool>(expression);
                    if (!ex.Evaluate())
                        list.Add(s);
                }

                if (exists)
                    this.ExpressionContext.Variables[variableName] = oldValue;
                else
                    this.ExpressionContext.Variables.Remove(variableName);

                return list.ToArray();
            }

            public DictionaryEntry[] RejectDict(IDictionary a, string variableName, string expression)
            {
                List<DictionaryEntry> list = new List<DictionaryEntry>();

                object oldValue = null;
                bool exists = false;

                if ((exists = this.ExpressionContext.Variables.ContainsKey(variableName)))
                    oldValue = this.ExpressionContext.Variables[variableName];

                bool created = exists;
                foreach (DictionaryEntry s in a)
                {
                    if (!created)
                    {
                        this.ExpressionContext.Variables.DefineVariable(variableName, typeof(DictionaryEntry));
                        this.ExpressionContext.Variables[variableName] = s;
                        created = true;
                    }
                    else
                        this.ExpressionContext.Variables[variableName] = s;

                    IGenericExpression<bool> ex = this.ExpressionContext.CompileGeneric<bool>(expression);
                    if (!ex.Evaluate())
                        list.Add(s);
                }

                if (exists)
                    this.ExpressionContext.Variables[variableName] = oldValue;
                else
                    this.ExpressionContext.Variables.Remove(variableName);

                return list.ToArray();
            }

            #endregion

            #region Conversion from Array (ToDict) -> Hashtable

            public Hashtable ToDict(params object[] args)
            {
                if (args.Length % 2 != 0)
                    throw new ArgumentOutOfRangeException("args");

                Hashtable hash = new Hashtable();
                for (int i = 0; i < args.Length; )
                    hash.Add(args[i++], args[i++]);

                return hash;
            }

            #endregion

            #region Array creation (FromRange, FromEnumerable, FromDict, FromList) -> []

            public char[] FromRange(char from, char to, char step)
            {
                if (from > to)
                    throw new ArgumentOutOfRangeException("from");

                List<char> list = new List<char>();
                for (char i = from; i <= to; i += step)
                    list.Add(i);

                return list.ToArray<char>();
            }

            public char[] FromRange(char from, char to)
            {
                return this.FromRange(from, to, (char)1);
            }

            public int[] FromRange(int from, int to, int step)
            {
                if (from > to)
                    throw new ArgumentOutOfRangeException("from");

                List<int> list = new List<int>();
                for (int i = from; i <= to; i += step)
                    list.Add(i);

                return list.ToArray<int>();
            }

            public int[] FromRange(int from, int to)
            {
                return this.FromRange(from, to, 1);
            }

            public DateTime[] FromRange(DateTime from, DateTime to, int step)
            {
                if (DateTime.Compare(from, to) > 0)
                    throw new ArgumentOutOfRangeException("from");

                List<DateTime> list = new List<DateTime>();
                for (int i = 0; i < to.Subtract(from).Days; i += step)
                    list.Add(from.AddDays(i));

                return list.ToArray<DateTime>();
            }

            public DateTime[] FromRange(DateTime from, DateTime to)
            {
                return this.FromRange(from, to, 1);
            }

            public DateTime[] FromRange(DateTime from, DateTime to, DateTimePart part)
            {
                return this.FromRange(from, to, 1, part);
            }

            public DateTime[] FromRange(DateTime from, DateTime to, int step, DateTimePart part)
            {
                if (DateTime.Compare(from, to) > 0)
                    throw new ArgumentOutOfRangeException("from");

                List<DateTime> list = new List<DateTime>();
                switch (part)
                {
                    case DateTimePart.Hours:
                        for (int i = 0; i < to.Subtract(from).Hours; i += step)
                            list.Add(from.AddHours(i));
                        break;
                    case DateTimePart.Minutes:
                        for (int i = 0; i < to.Subtract(from).Minutes; i += step)
                            list.Add(from.AddMinutes(i));
                        break;
                    case DateTimePart.Seconds:
                        for (int i = 0; i < to.Subtract(from).Seconds; i += step)
                            list.Add(from.AddSeconds(i));
                        break;
                    case DateTimePart.Months:
                        int months = ((to.Year - from.Year - 1) * 12) + (12 - from.Month + to.Month);
                        for (int i = 0; i < months; i += step)
                            list.Add(from.AddMonths(i));
                        break;
                    case DateTimePart.Years:
                        for (int i = 0; i < (to.Year - from.Year); i += step)
                            list.Add(from.AddYears(i));
                        break;
                    default:
                        for (int i = 0; i < to.Subtract(from).Days; i += step)
                            list.Add(from.AddDays(i));
                        break;
                }

                return list.ToArray<DateTime>();
            }

            public object[] FromEnumerable(IEnumerable e)
            {
                IEnumerator enum1 = e.GetEnumerator();
                ArrayList list = new ArrayList();
                while (enum1.MoveNext())
                    list.Add(enum1.Current);

                return list.ToArray();
            }

            public object[] FromDict(IDictionary dict)
            {
                ArrayList list = new ArrayList();
                foreach (DictionaryEntry e in dict)
                {
                    list.Add(e.Key);
                    list.Add(e.Value);
                }
                return list.ToArray();
            }

            private T[] FromList<T>(params T[] args)
            {
                return args;
            }

            public DateTime[] FromList(params DateTime[] args)
            {
                return this.FromList<DateTime>(args);
            }

            public Int16[] FromList(params Int16[] args)
            {
                return this.FromList<Int16>(args);
            }

            public Int32[] FromList(params Int32[] args)
            {
                return this.FromList<Int32>(args);
            }

            public Int64[] FromList(params Int64[] args)
            {
                return this.FromList<Int64>(args);
            }

            public object[] FromList(params object[] args)
            {
                return this.FromList<object>(args);
            }

            public string[] FromList(params string[] args)
            {
                return this.FromList<string>(args);
            }

            public SByte[] FromList(params SByte[] args)
            {
                return this.FromList<SByte>(args);
            }

            public Single[] FromList(params Single[] args)
            {
                return this.FromList<Single>(args);
            }

            public double[] FromList(params double[] args)
            {
                return this.FromList<double>(args);
            }

            public decimal[] FromList(params decimal[] args)
            {
                return this.FromList<decimal>(args);
            }

            public bool[] FromList(params bool[] args)
            {
                return this.FromList<bool>(args);
            }

            public byte[] FromList(params byte[] args)
            {
                return this.FromList<byte>(args);
            }

            public char[] FromList(params char[] args)
            {
                return this.FromList<char>(args);
            }

            public string[] FromString(string text, params string[] args)
            {
                string[] separators;

                if (args.Length == 0)
                    separators = new string[] { " " };
                else
                    separators = args;

                return text.Split(separators, StringSplitOptions.RemoveEmptyEntries);
            }

            #endregion

            #region Concat (Concat, ConcatDist, ConcatDict) -> string
            
            public string Concat(IEnumerable a, string variableName, string expression, string separator)
            {
                string result = String.Empty;

                object oldValue = null;
                bool exists = false;

                if ((exists = this.ExpressionContext.Variables.ContainsKey(variableName)))
                    oldValue = this.ExpressionContext.Variables[variableName];

                bool created = exists;
                foreach (object s in a)
                {
                    if (!created)
                    {
                        this.ExpressionContext.Variables.Add(variableName, s);
                        created = true;
                    }
                    else
                        this.ExpressionContext.Variables[variableName] = s;

                    IDynamicExpression ex = this.ExpressionContext.CompileDynamic(expression);
                    result += ex.Evaluate() + separator;
                }
                result = result.Substring(0, result.Length - separator.Length);

                if (exists)
                    this.ExpressionContext.Variables[variableName] = oldValue;
                else
                    this.ExpressionContext.Variables.Remove(variableName);

                return result;
            }
            
            public string Concat(IEnumerable a, string variableName, string expression)
            {
                return this.Concat(a, variableName, expression, String.Empty);
            }
            
            public string ConcatDist(IEnumerable a, string variableName, string expression, string separator)
            {
                string result = String.Empty;

                object oldValue = null;
                bool exists = false;

                if ((exists = this.ExpressionContext.Variables.ContainsKey(variableName)))
                    oldValue = this.ExpressionContext.Variables[variableName];

                bool created = exists;
                foreach (object s in a)
                {
                    if (!created)
                    {
                        this.ExpressionContext.Variables.DefineVariable(variableName, typeof(object));
                        this.ExpressionContext.Variables[variableName] = s;
                        created = true;
                    }
                    else
                        this.ExpressionContext.Variables[variableName] = s;

                    IDynamicExpression ex = this.ExpressionContext.CompileDynamic(expression);
                    result += ex.Evaluate() + separator;
                }
                result = result.Substring(0, result.Length - separator.Length);

                if (exists)
                    this.ExpressionContext.Variables[variableName] = oldValue;
                else
                    this.ExpressionContext.Variables.Remove(variableName);

                return result;
            }
            
            public string ConcatDist(IEnumerable a, string variableName, string expression)
            {
                return this.ConcatDist(a, variableName, expression, String.Empty);
            }
            
            public string ConcatDict(IDictionary a, string variableName, string expression, string separator)
            {
                string result = String.Empty;

                object oldValue = null;
                bool exists = false;

                if ((exists = this.ExpressionContext.Variables.ContainsKey(variableName)))
                    oldValue = this.ExpressionContext.Variables[variableName];

                bool created = exists;
                foreach (DictionaryEntry s in a)
                {
                    if (!created)
                    {
                        this.ExpressionContext.Variables.DefineVariable(variableName, typeof(DictionaryEntry));
                        this.ExpressionContext.Variables[variableName] = s;
                        created = true;
                    }
                    else
                        this.ExpressionContext.Variables[variableName] = s;

                    IDynamicExpression ex = this.ExpressionContext.CompileDynamic(expression);
                    result += ex.Evaluate() + separator;
                }
                result = result.Substring(0, result.Length - separator.Length);

                if (exists)
                    this.ExpressionContext.Variables[variableName] = oldValue;
                else
                    this.ExpressionContext.Variables.Remove(variableName);

                return result;
            }

            public string ConcatDict(IDictionary a, string variableName, string expression)
            {
                return this.ConcatDict(a, variableName, expression, String.Empty);
            }

            #endregion

            #region Variables (Let, Remove)

            public object Let(string variableName, object value)
            {
                if (!this.ExpressionContext.Variables.ContainsKey(variableName))
                    this.ExpressionContext.Variables.Add(variableName, value);
                else
                    this.ExpressionContext.Variables[variableName] = value;

                return value;
            }

            public object Remove(string variableName)
            {
                object result = null;
                if (this.ExpressionContext.Variables.ContainsKey(variableName))
                {
                    result = this.ExpressionContext.Variables[variableName];
                    this.ExpressionContext.Variables.Remove(variableName);
                }
                return result;
            }

            #endregion

            #region Conditional (Choose, When) -> object

            // index > 0
            public object Choose(int index, params object[] args)
            {
                if (index < 1)
                    throw new ArgumentOutOfRangeException("index");

                if (args.Length == 0)
                    throw new ArgumentOutOfRangeException("args");

                if (index > args.Length)
                    return args[args.Length - 1];

                return args[index - 1];
            }

            public object[] When(object test, params object[] args)
            {
                ArrayList list = new ArrayList();
                ArrayList toTest, values;

                switch (args.Length)
                {
                    case 0:
                        throw new ArgumentOutOfRangeException("args");
                    case 2:
                        // object[] When(object test, object[] toTest, object[] values)
                        if (!(args[0].GetType().IsArray && args[1].GetType().IsArray))
                            throw new ArgumentException();

                        toTest = new ArrayList((ICollection)args[0]);
                        values = new ArrayList((ICollection)args[1]);

                        if (toTest.Count != values.Count)
                            throw new ArgumentOutOfRangeException();
                        
                        for (int i = 0; i < toTest.Count; i++)
                            if (toTest[i].ToString().Equals(test.ToString(), StringComparison.InvariantCultureIgnoreCase))
                                list.Add(values[i]);
                        break;
                    case 3:
                        // object[] When(object test, object defaultValue, object[] toTest, object[] values)
                        if (!(args[0] != null && args[1].GetType().IsArray && args[2].GetType().IsArray))
                            throw new ArgumentException();

                        toTest = new ArrayList((ICollection)args[1]);
                        values = new ArrayList((ICollection)args[2]);

                        if(toTest.Count != values.Count)
                            throw new ArgumentOutOfRangeException();

                        for (int i = 0; i < toTest.Count; i++)
                            if (toTest[i].ToString().Equals(test.ToString(), StringComparison.InvariantCultureIgnoreCase))
                                list.Add(values[i]);

                        if (list.Count == 0)
                            list.Add(args[0]);

                        break;
                    default:
                        // couples (test, valore)
                        if (args.Length % 2 == 1)
                            throw new ArgumentOutOfRangeException("args");

                        for (int i = 0; i < args.Length; )
                            if (args[i++].ToString().Equals(test.ToString(), StringComparison.InvariantCultureIgnoreCase))
                                list.Add(args[i++]);
                            else
                                i++;

                        break;
                }
                
                return list.ToArray();
            }

            #endregion

            #region Escape -> string

            public string Escape(string text)
            {
                return text.Replace("'", "\"");
            }

            #endregion

            #endregion
        }

        #endregion
    }

    #region Expressions enum

    public enum DateTimePart
    {
        Days = 0,
        Months = 1,
        Years = 2,
        Hours = 3,
        Minutes = 4,
        Seconds = 5
    }

    #endregion
}
